#include "short.h"
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/cdev.h>

uint base = QEMU_DEBUG_PORT;
module_param(base, uint, S_IRUGO);
MODULE_PARM_DESC(base, "Base I/O port address (default 0xe9 for QEMU debug)");
bool use_mem = false;
module_param(use_mem, bool, S_IRUGO);
MODULE_PARM_DESC(use_mem, "Use memory-mapped I/O instead of port I/O");

dev_t dev;
struct class *short_class;
struct short_dev *short_device;

static int short_open(struct inode *inode, struct file *filp)
{
    pr_info("short: Device opened\n");
    struct cdev *cdev = inode->i_cdev;
    struct short_dev *sdev = container_of(cdev, struct short_dev, cdev);

    filp->private_data = sdev;
    return 0;
}

static ssize_t short_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    pr_info("short: Read %zu bytes from position %lld\n", count, *f_pos);
    struct short_dev *sdev = filp->private_data;
    size_t n = 0;

    if (down_interruptible(&sdev->sem))
        return -ERESTARTSYS;

    while (count > 0) {
        unsigned char data;
        if (use_mem) {
            data = ioread8(sdev->iomem_base);
        } else {
            data = inb(sdev->base_addr);
        }
        pr_info("short: Read byte 0x%02x from port 0x%lx\n", data, sdev->base_addr);
        rmb(); // Ensure the read is completed
        if (copy_to_user(buf, &data, 1)) {
            return -EFAULT;
        }
        buf++;
        count--;
        n++;
        (*f_pos)++;
    }
    up(&sdev->sem);

    pr_info("short: Total bytes read %zu\n", n);
    return n;
}

static ssize_t short_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    pr_info("short: Write %zu bytes to position %lld\n", count, *f_pos);
    struct short_dev *sdev = filp->private_data;
    size_t n = 0;

    if (down_interruptible(&sdev->sem))
        return -ERESTARTSYS;

    while (count > 0) {
        unsigned char data;
        if (copy_from_user(&data, buf, 1)) {
            return -EFAULT;
        }
        pr_info("short: Writing byte 0x%02x to port 0x%lx\n", data, sdev->base_addr);
        if (use_mem) {
            iowrite8(data, sdev->iomem_base);
        } else {
            outb(data, sdev->base_addr);
        }
        wmb(); // Ensure the write is completed
        buf++;
        count--;
        n++;
        (*f_pos)++;
    }
    up(&sdev->sem);

    pr_info("short: Total bytes written %zu\n", n);
    return n;
}

static int short_release(struct inode *inode, struct file *filp)
{
    pr_info("short: Device closed\n");
    return 0;
}

static struct file_operations short_fops = {
    .owner = THIS_MODULE,
    .open = short_open,
    .read = short_read,
    .write = short_write,
    .release = short_release,
};

static int __init short_init(void)
{
    pr_info("short: Initialization started\n");
    struct device *device;
    struct resource *res;
    int result;

    // Allocate device numbers
    result = alloc_chrdev_region(&dev, 0, SHORT_NR_DEVS, SHORT_NAME);
    if (result < 0) {
        pr_err("short: Failed to allocate char device region\n");
        return result;
    }
    pr_info("short: Allocated device number %d:%d\n", MAJOR(dev), MINOR(dev));

    short_class = class_create(SHORT_CLASS_NAME);
    if (IS_ERR(short_class)) {
        pr_err("short: Failed to create class\n");
        result = PTR_ERR(short_class);
        goto add_cdev_err;
    }
    pr_info("short: Created device class\n");

    short_device = kzalloc(sizeof(struct short_dev), GFP_KERNEL);
    if (!short_device) {
        pr_err("short: Failed to allocate cdev\n");
        result = -ENOMEM;
        goto alloc_cdev_err;
    }
    cdev_init(&short_device->cdev, &short_fops);
    short_device->cdev.owner = THIS_MODULE;
    short_device->base_addr = base;
    sema_init(&short_device->sem, 1);

    res = request_region(base, 1, SHORT_NAME);
    if (!res) {
        pr_err("short: I/O port 0x%x busy\n", base);
        result = -EBUSY;
        goto request_region_err;
    }
    if (use_mem) {
        short_device->iomem_base = ioport_map(base, 1);
        if (!short_device->iomem_base) {
            pr_err("short: Failed to ioremap memory at 0x%lx\n", (unsigned long)base);
            result = -EIO;
            goto request_region_err;
        }
        pr_info("short: Mapped memory at 0x%p\n", short_device->iomem_base);
    }

    result = cdev_add(&short_device->cdev, dev, 1);
    if (result) {
        pr_err("short: Failed to add cdev\n");
        goto cdev_add_err;
    }
    device = device_create(short_class, NULL, dev, NULL, SHORT_NAME "%d", 0);
    if (IS_ERR(device)) {
        pr_err("short: Failed to create device\n");
        result = PTR_ERR(device);
        goto device_create_err;
    }
    pr_info("short: Device created successfully\n");
    pr_info("short: Initialization completed\n");
    return 0;

device_create_err:
    cdev_del(&short_device->cdev);
cdev_add_err:
    release_region(base, 1);
request_region_err:
    kfree(short_device);
alloc_cdev_err:
    class_destroy(short_class);
add_cdev_err:
    unregister_chrdev_region(dev, 1);
    pr_info("short: Initialization failed\n");
    return result;
}
module_init(short_init);

static void __exit short_exit(void)
{
    pr_info("short: Exiting module\n");
    device_destroy(short_class, dev);
    cdev_del(&short_device->cdev);
    ioport_unmap(short_device->iomem_base);
    release_region(base, 1);
    kfree(short_device);
    class_destroy(short_class);
    unregister_chrdev_region(dev, 1);
    pr_info("short: Module exited successfully\n");
}
module_exit(short_exit);

MODULE_LICENSE("GPL");